#include <scheme.h>
#include "../../include/path_def.h"
#include "../../include/xobject.h"
#include "data_type.h"
#include "crypto_func.h"
#include "sm3.h"
#include "policymanage.h"

#include "policy_buffer.h"

#define f_dentry        f_path.dentry


extern unsigned long volatile jiffies;

int deep_debug = 0;

typedef struct __POLICY_SET
{
	XOBJECT objHdr;
	int digest_num;
	int digest_size;
	BYTE * digest_array;
} POLICY_SET;

void set_current_policy_set(POLICY_SET * set);
POLICY_SET * get_current_policy_set(void);
void free_whitelist(void);
// -----------------------------------------------------------------
static spinlock_t g_policy_lock;
static POLICY_SET * g_policy_buffer = NULL;

int policy_module_init(void)
{
	spin_lock_init(&g_policy_lock);
	return 0;
}

void policy_module_destroy(void)
{
	free_whitelist();
	return;
}

POLICY_SET * get_current_policy_set(void)
{
	POLICY_SET * set = NULL;
	
	spin_lock(&g_policy_lock);	
	if(g_policy_buffer){
		set = g_policy_buffer;
		xobject_addref(set);
	}
	spin_unlock(&g_policy_lock);	
	return set;
}

void set_current_policy_set(POLICY_SET * set)
{
	POLICY_SET * old = NULL;
	
	spin_lock(&g_policy_lock);
	
	if(g_policy_buffer){
		old = g_policy_buffer;
		g_policy_buffer = NULL;
	}
	
	if(set){
		g_policy_buffer = set;
		xobject_addref(set);
	}
	spin_unlock(&g_policy_lock);	
	
	if(old){
		xobject_release(old);
	}
	return ;	
}

static void __clean_whitelist(POLICY_SET * policys)
{ 
	if(!policys) return;
	
	if(policys->digest_array){
		kfree(policys->digest_array);
	}
	policys->digest_array = NULL;
	policys->digest_num=0;	
	return;
}

static void __destroyPolicySet(POLICY_SET * set)
{
	__clean_whitelist(set);
	kfree(set);
	return;
}
 
 
static POLICY_SET * createPolicySet(void)
{
	POLICY_SET * set = (POLICY_SET *)kmalloc(sizeof(POLICY_SET), GFP_KERNEL);
	
	if(!set){
		return NULL;
	}
	memset(set, 0, sizeof(POLICY_SET));
	set->objHdr.destroyPtr = (DestroyAtomPtr) __destroyPolicySet;
 
	xobject_addref(set);
	return set;
}


static int
compare_hash(const void *a,const void *b)
{
    if(deep_debug){
		unsigned char * test_buf=b;
		printk("debug compare_hash %x %x %x\n",test_buf[0],test_buf[1],test_buf[2]);
	}
	return memcmp(a,b,DIGEST_SIZE);
}

int read_whitelist(const char * filename, const int max_number, int size)
{
	int retval = -1;
	POLICY_SET * policys = NULL;
	struct file * fp = NULL;
	mm_segment_t oldfs;
	int isOldFsValid = 0;
	char * kernel_buffer = NULL;
	int i = 0;
	loff_t fsize;
	int number = 0;

	policys = createPolicySet();
	if(!policys) goto DONE;
	
	policys->digest_size = size;
	
	fp = filp_open(filename, O_RDONLY | O_SYNC, 0); 
	// -- filp_open(filename, O_RDONLY, 0);
	if(IS_ERR(fp)) {
		printk("open policy file 1 %s error!\n", filename);
		fp = NULL;
 
		goto DONE;
	}
	
 	printk("<!> open policy file 2 (%s) success!\n", filename);
 
	#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) 
		fsize = i_size_read(fp->f_path.dentry->d_inode);
	#else
		fsize = i_size_read(fp->f_dentry->d_inode);
	#endif
	// -- fsize = fp->f_dentry->d_inode->i_size;
	if (((fsize % size) == 0) && fsize < (32*40000)){
		number = fsize /size;
	}else{
		goto DONE;
	}
    
	printk("read hashlist app num is %d max %d\n", number, max_number);
	
	policys->digest_num = number;
	policys->digest_array = kmalloc(size* number, GFP_KERNEL);
	if(!policys->digest_array){
		printk("alloc digest policy buffer error!\n");
		retval = -ENOMEM;
		goto DONE;
		// -- memset(policys->digest_array,0,policys->digest_num*policys->digest_size);
	}

	kernel_buffer = kmalloc(size, GFP_KERNEL);
	// -- memset(user_buffer,0,size);
	if(!kernel_buffer) {	
		printk("alloc mem for read config file err!\n");
		retval = -ENOMEM;	
		goto DONE;
	}

	isOldFsValid = 1;
	oldfs = get_fs();
	set_fs (KERNEL_DS);
	
	for (i=0; i < number;i++){
		int ret = vfs_read(fp, kernel_buffer, size, &fp->f_pos);
			
		if(ret != size) {
			printk("read [%s] error!\n", filename);
			goto DONE;		
		}
		memcpy(policys->digest_array + size * i, kernel_buffer, size);	
	}
	
	set_current_policy_set(policys);
	retval = 0;
DONE:	
	if(policys){
		xobject_release(policys);
		policys = NULL;
	}
	 
	if(kernel_buffer){
		kfree(kernel_buffer);
	}
	
	if(isOldFsValid){
		set_fs(oldfs);
	}
	
	if(fp){
		filp_close(fp, 0);
	}
	return retval; 
}

void free_whitelist(void)
{
	POLICY_SET * set = NULL;
	
	spin_lock(&g_policy_lock);	
	if(g_policy_buffer){
		set = g_policy_buffer; 
		g_policy_buffer = NULL;
	}
	spin_unlock(&g_policy_lock);	
	if(set){
		xobject_release(set);
	}
	return ;
}

int alg_file_digest (struct file *fp,unsigned char xh_digest[DIGEST_SIZE])
{
	int i;
	sm3_context_ext context;
	mm_segment_t oldfs;
	int len;

	unsigned char *kern_buf;
	loff_t pos = 0;

	if(IS_ERR(fp) || fp == NULL ) {
		printk("alg_check fp is null\n");		
		return -1;
	}	
	sm3_starts_ext(&context);
	pos = fp->f_pos;
	fp->f_pos = 0;
	kern_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
	oldfs = get_fs();
	set_fs (KERNEL_DS);
	len = vfs_read (fp,kern_buf, PAGE_SIZE,&fp->f_pos);
	while (len > 0)
	{
		sm3_update_ext (&context, kern_buf, len);
		len = vfs_read (fp,kern_buf, PAGE_SIZE,&fp->f_pos);
	}
	set_fs(oldfs);
	sm3_finish_ext(&context,xh_digest);
	kfree(kern_buf);

	fp->f_pos = pos;
	return 0;

}

// found return entry;non-found return NULl
void *
appbsearch(const void *key, const void *base, size_t nmemb, size_t size,
		int (*compar)(const void *, const void *))
{
	const void *entry;
	unsigned int l;
	unsigned int u;
	unsigned int m;

	l = -1;
	u = nmemb;
	while (l + 1 != u) {
		m = (l + u) / 2;
		entry = base + m * size;
		if (compar(key, entry) > 0)
			l = m;
		else
			u = m;
	}

	entry = base + u * size;
	//printk("num:%d,dig_size:%d\n",nmemb,size);
	if (u == nmemb
			|| compar(key, entry) != 0)
		return (NULL);

	return ((void *)entry);
}

int pm_check_fs_integrity(struct file *fp)
{
	unsigned char digest[DIGEST_SIZE];
    POLICY_SET * set = get_current_policy_set();
	int ret = 0;
	
	if (!fp){
		goto DONE;
	}
	
	if(!set){
		goto DONE;
	}
	
	alg_file_digest(fp, digest);
	
	if(deep_debug)
		printk("debug file hash  %x %x %x\n",digest[0],digest[1],digest[2]);
	
	if (!appbsearch(digest, set->digest_array, set->digest_num,DIGEST_SIZE, compare_hash)){
		ret = -1;
		// -- ret = 0;
	}
DONE:
	if(set){
		xobject_release(set);
	}
	return ret;
}


